package org.yaac.client.ui;

import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.yaac.client.util.ListBoxUtil;
import org.yaac.client.widget.SortHandler;
import org.yaac.client.widget.SortableColumn;
import org.yaac.client.widget.stat.CountSizeFilterChoice;
import org.yaac.client.widget.stat.CountSizeMultiFilterPanel;
import org.yaac.client.widget.stat.CountSizeSingleFilterPanel;
import org.yaac.client.widget.stat.KindFilterChoice;
import org.yaac.client.widget.stat.KindMultiFilterPanel;
import org.yaac.client.widget.stat.KindSingleFilterPanel;

import com.google.common.collect.ComparisonChain;
import com.google.gwt.cell.client.CheckboxCell;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.DefaultSelectionEventManager;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.LegendPosition;
import com.google.gwt.visualization.client.VisualizationUtils;
import com.google.gwt.visualization.client.visualizations.BarChart;
import com.google.gwt.visualization.client.visualizations.ColumnChart;
import com.google.gwt.visualization.client.visualizations.PieChart;

/**
 * @author Max Zhu (thebbsky@gmail.com)
 *
 */
// TODO : make chart resizable
// TODO : logScale support?
// TODO : breakdown by property name
public class StatisticsViewImpl extends Composite implements StatisticsView {
	private static StatisticsViewImplUiBinder uiBinder = GWT.create(StatisticsViewImplUiBinder.class);

	@UiTemplate("StatisticsView.ui.xml")
	interface StatisticsViewImplUiBinder extends UiBinder<Widget, StatisticsViewImpl> {
	}

	private static final String CHART_TYPE_BAR = "bar";
	
	private static final String CHART_TYPE_PIE = "pie";
	
	private static final String CHART_TYPE_COLUMN = "column";
	
	/**
	 * config panel index for pie chart
	 */
	private static final Integer CONFIG_IDX_PIE = 0;
	
	/**
	 * config panel index for bar and column chart
	 */
	private static final Integer CONFIG_IDX_BAR_COLUMN = 1;
	
	// TODO : i18n
	private static final String SELECT_ALL = "[ALL]";
	
	// =============radio button groups used for corresponding filter panels================
	private final String NAMESPACE_RADIO_GROUP = "nsg";
	
	private final String PROPERTY_TYPE_RADIO_GROUP = "ptg";
	
	private final String KIND_RADIO_GROUP = "kg";
	
	@UiField
	ListBox chartTypeChoice;
	
	@UiField
	Element descElem;
	
	/**
	 * filter by namespace
	 */
	@UiField
	ListBox namespaceChoice;
	
	/**
	 * filter by kind name
	 */
	@UiField
	ListBox kindChoice;
	
	@UiField
	SimplePanel namespaceChart;
	
	@UiField
	DeckPanel namespaceChartConfig;
	
	private CountSizeSingleFilterPanel namespaceSingleConfig;
	
	private CountSizeMultiFilterPanel namespaceMultiConfig; 
	
	/**
	 * won't be changed once initialized
	 */
	@UiField(provided=true)
	CellTable<NamespaceBreakdown> namespaceTable;
	
	/**
	 * selection model, used to render chart
	 */
	private SelectionModel<NamespaceBreakdown> namespaceSelectionModel;
	
	/**
	 * sort handler, will not be changed once initialized
	 */
	private SortHandler<NamespaceBreakdown> namespaceSortHandler;
	
	/**
	 * display data, keep changing
	 */
	private List<NamespaceBreakdown> namespaceBreakdownList;
	
	@UiField
	SimplePanel propertyTypeChart;
	
	@UiField
	DeckPanel propertyTypeChartConfig;
	
	private CountSizeSingleFilterPanel propertyTypeSingleConfig;
	
	private CountSizeMultiFilterPanel propertyTypeMultiConfig;
	
	/**
	 * won't be changed once initialized
	 */
	@UiField(provided=true)
	CellTable<PropertyTypeBreakdown> propertyTypeTable;
	
	/**
	 * selection model, used to render chart
	 */
	private SelectionModel<PropertyTypeBreakdown> propertySelectionModel;
	
	/**
	 * sort handler, will not be changed once initialized
	 */
	private SortHandler<PropertyTypeBreakdown> propertySortHandler;
	
	/**
	 * display data, keep changing
	 */
	private List<PropertyTypeBreakdown> propertyBreakdownList;
	
	@UiField
	SimplePanel kindChart;
	
	@UiField
	DeckPanel kindChartConfig;
	
	private KindSingleFilterPanel kindSingleConfig;
	
	private KindMultiFilterPanel kindMultiConfig;
	
	/**
	 * won't be changed once initialized
	 */
	@UiField(provided=true)
	CellTable<KindBreakdown> kindTable; 
	
	/**
	 * selection model, used to render chart
	 */
	private SelectionModel<KindBreakdown> kindSelectionModel;
	
	/**
	 * sort handler, will not be changed once initialized
	 */
	private SortHandler<KindBreakdown> kindSortHandler;
	
	/**
	 * display data, keep changing
	 */
	private List<KindBreakdown> kindBreakdownList;
	
	// activity
	private Presenter listener;
	
	@Inject
	StatisticsViewImpl() {
		// ======init provided UI widgets========
		
		// init namespace table
		{
			namespaceSortHandler = new SortHandler<NamespaceBreakdown>();
			
			namespaceTable = new CellTable<NamespaceBreakdown>();
			namespaceTable.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
			namespaceTable.addColumnSortHandler(namespaceSortHandler);
			namespaceTable.setVisibleRange(0, Integer.MAX_VALUE);
			
			namespaceSelectionModel = 
				new MultiSelectionModel<NamespaceBreakdown>(NamespaceBreakdown.KEY_PROVIDER);
			namespaceTable.setSelectionModel(namespaceSelectionModel, 
					DefaultSelectionEventManager.<NamespaceBreakdown>createCheckboxManager());
			
			namespaceSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
				@Override
				public void onSelectionChange(SelectionChangeEvent event) {
					refreshNamespaceChart();
				}
			});
			
			// checkbox for chart rendering, unsortable
			{
			    Column<NamespaceBreakdown, Boolean> checkColumn = new Column<NamespaceBreakdown, Boolean>(
			            new CheckboxCell(true, false)) {
			          @Override
			          public Boolean getValue(NamespaceBreakdown object) {
			        	  return namespaceSelectionModel.isSelected(object);
			          }
			    };
			    
			    //empty nothing
			    namespaceTable.addColumn(checkColumn, SafeHtmlUtils.fromSafeConstant("<br/>"));	
			}
			
			// namespace column
			{
				SortableColumn<NamespaceBreakdown, String> column = 
					new SortableColumn<NamespaceBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(NamespaceBreakdown breakdown) {
						return breakdown.getNamespace();
					}
				};
				
				namespaceTable.addColumn(column, "Namespace");
				
				namespaceSortHandler.setComparator(column, new Comparator<NamespaceBreakdown>() {
					@Override
					public int compare(NamespaceBreakdown o1, NamespaceBreakdown o2) {
						return o1.getNamespace().compareTo(o2.getNamespace());
					}
				});
			}
			
			// entity count
			{
				SortableColumn<NamespaceBreakdown, String> column = 
					new SortableColumn<NamespaceBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(NamespaceBreakdown breakdown) {
						return breakdown.getCount().toString();
					}
				};
				
				namespaceTable.addColumn(column, "Count");
				
				namespaceSortHandler.setComparator(column, new Comparator<NamespaceBreakdown>() {
					@Override
					public int compare(NamespaceBreakdown o1, NamespaceBreakdown o2) {
						return o1.getCount().compareTo(o2.getCount());
					}
				});
			}
			
			// entity size
			{
				SortableColumn<NamespaceBreakdown, String> column = 
					new SortableColumn<NamespaceBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(NamespaceBreakdown breakdown) {
						return breakdown.getBytes().toString();
					}
				};
				
				namespaceTable.addColumn(column, "Size(Bytes)");
				
				namespaceSortHandler.setComparator(column, new Comparator<NamespaceBreakdown>() {
					@Override
					public int compare(NamespaceBreakdown o1, NamespaceBreakdown o2) {
						return o1.getBytes().compareTo(o2.getBytes());
					}
				});
			}
		}
		
		// init property type table
		{
			propertySortHandler = new SortHandler<PropertyTypeBreakdown>();
			
			propertyTypeTable = new CellTable<PropertyTypeBreakdown>();
			propertyTypeTable.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
			propertyTypeTable.addColumnSortHandler(propertySortHandler);
			propertyTypeTable.setVisibleRange(0, Integer.MAX_VALUE);
			
			propertySelectionModel = 
				new MultiSelectionModel<PropertyTypeBreakdown>(PropertyTypeBreakdown.KEY_PROVIDER);
			propertyTypeTable.setSelectionModel(propertySelectionModel, 
					DefaultSelectionEventManager.<PropertyTypeBreakdown>createCheckboxManager());
			
			propertySelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
				@Override
				public void onSelectionChange(SelectionChangeEvent event) {
					refreshPropertyTypeChart();
				}
			});
			
			// checkbox for chart rendering, unsortable
			{
			    Column<PropertyTypeBreakdown, Boolean> checkColumn = new Column<PropertyTypeBreakdown, Boolean>(
			            new CheckboxCell(true, false)) {
			          @Override
			          public Boolean getValue(PropertyTypeBreakdown object) {
			        	  return propertySelectionModel.isSelected(object);
			          }
			    };
			    
			    //empty nothing
			    propertyTypeTable.addColumn(checkColumn, SafeHtmlUtils.fromSafeConstant("<br/>"));	
			}
			
			// property type
			{
				SortableColumn<PropertyTypeBreakdown, String> column = 
					new SortableColumn<PropertyTypeBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(PropertyTypeBreakdown property) {
						return property.getPropertyType();
					}
				};
				
				propertyTypeTable.addColumn(column, "Type");
				
				propertySortHandler.setComparator(column, new Comparator<PropertyTypeBreakdown>() {
					@Override
					public int compare(PropertyTypeBreakdown o1, PropertyTypeBreakdown o2) {
						return o1.getPropertyType().compareTo(o2.getPropertyType());
					}
				});
			}
			
			// count
			{
				SortableColumn<PropertyTypeBreakdown, String> column = 
					new SortableColumn<PropertyTypeBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(PropertyTypeBreakdown property) {
						return property.getCount().toString();
					}
				};
				
				propertyTypeTable.addColumn(column, "Count");
				
				propertySortHandler.setComparator(column, new Comparator<PropertyTypeBreakdown>() {
					@Override
					public int compare(PropertyTypeBreakdown o1, PropertyTypeBreakdown o2) {
						return o1.getCount().compareTo(o2.getCount());
					}
				});
			}
			
			// size
			{
				SortableColumn<PropertyTypeBreakdown, String> column = 
					new SortableColumn<PropertyTypeBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(PropertyTypeBreakdown property) {
						return property.getBytes().toString();
					}
				};
				
				propertyTypeTable.addColumn(column, "Size(Bytes)");
				
				propertySortHandler.setComparator(column, new Comparator<PropertyTypeBreakdown>() {
					@Override
					public int compare(PropertyTypeBreakdown o1, PropertyTypeBreakdown o2) {
						return o1.getBytes().compareTo(o2.getBytes());
					}
				});
			}
		}
		
		// init kinds table with sorting handlers
		{
			kindSortHandler = new SortHandler<KindBreakdown>();
			
			kindTable = new CellTable<KindBreakdown>();
			kindTable.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
			kindTable.addColumnSortHandler(kindSortHandler);
			kindTable.setVisibleRange(0, Integer.MAX_VALUE);
			
			kindSelectionModel = 
				new MultiSelectionModel<KindBreakdown>(KindBreakdown.KEY_PROVIDER);
			kindTable.setSelectionModel(kindSelectionModel, 
					DefaultSelectionEventManager.<KindBreakdown>createCheckboxManager());
			
			kindSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
				@Override
				public void onSelectionChange(SelectionChangeEvent event) {
					refreshKindChart();
				}
			});
			
			// checkbox for chart rendering, unsortable
			{
			    Column<KindBreakdown, Boolean> checkColumn = new Column<KindBreakdown, Boolean>(
			            new CheckboxCell(true, false)) {
			          @Override
			          public Boolean getValue(KindBreakdown object) {
			        	  return kindSelectionModel.isSelected(object);
			          }
			    };
			    
			    //empty nothing
			    kindTable.addColumn(checkColumn, SafeHtmlUtils.fromSafeConstant("<br/>"));	
			}
			
			// name column
			{
				SortableColumn<KindBreakdown, String> column = 
					new SortableColumn<KindBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(KindBreakdown kind) {
						return kind.getKindName();
					}
				};
				
				kindTable.addColumn(column, "Name");;
				
				kindSortHandler.setComparator(column, new Comparator<KindBreakdown>() {
					@Override
					public int compare(KindBreakdown arg0, KindBreakdown arg1) {
						return arg0.getKindName().compareTo(arg1.getKindName());
					}
				});
			}
			
			// count column for all entitities
			{
				SortableColumn<KindBreakdown, String> column = 
					new SortableColumn<KindBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(KindBreakdown kind) {
						return kind.getCount().toString();
					}
				};
				
				kindTable.addColumn(column, "Count(All)");;
				
				kindSortHandler.setComparator(column, new Comparator<KindBreakdown>() {
					@Override
					public int compare(KindBreakdown arg0, KindBreakdown arg1) {
						return arg0.getCount().compareTo(arg1.getCount());
					}
				});
			}
			
			// size all 
			{
				SortableColumn<KindBreakdown, String> column = 
					new SortableColumn<KindBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(KindBreakdown kind) {
						return kind.getSize().toString();
					}
				};
				
				kindTable.addColumn(column, "Size(All)");;
				
				kindSortHandler.setComparator(column, new Comparator<KindBreakdown>() {
					@Override
					public int compare(KindBreakdown arg0, KindBreakdown arg1) {
						return arg0.getSize().compareTo(arg1.getSize());
					}
				});
			}
			
			// count for root entity
			{
				SortableColumn<KindBreakdown, String> column = 
					new SortableColumn<KindBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(KindBreakdown kind) {
						return kind.getRootCount().toString();
					}
				};
				
				kindTable.addColumn(column, "Count(Root)");
				
				kindSortHandler.setComparator(column, new Comparator<KindBreakdown>() {
					@Override
					public int compare(KindBreakdown arg0, KindBreakdown arg1) {
						return ComparisonChain.start().compare(arg0.getRootCount(), arg1.getRootCount()).result();
					}
				});
			}
			
			// size for root entity
			{
				SortableColumn<KindBreakdown, String> column = 
					new SortableColumn<KindBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(KindBreakdown kind) {
						return kind.getRootSize().toString();
					}
				};
				
				kindTable.addColumn(column, "Size(Root)");
				
				kindSortHandler.setComparator(column, new Comparator<KindBreakdown>() {
					@Override
					public int compare(KindBreakdown arg0, KindBreakdown arg1) {
						return ComparisonChain.start().compare(arg0.getRootSize(), arg1.getRootSize()).result();
					}
				});
			}
			
			// count for nonroot entity
			{
				SortableColumn<KindBreakdown, String> column = 
					new SortableColumn<KindBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(KindBreakdown kind) {
						return kind.getNonRootCount().toString();
					}
				};
				
				kindTable.addColumn(column, "Count(Non Root)");
				
				kindSortHandler.setComparator(column, new Comparator<KindBreakdown>() {
					@Override
					public int compare(KindBreakdown arg0, KindBreakdown arg1) {
						return ComparisonChain.start().compare(
								arg0.getNonRootCount(), arg1.getNonRootCount()).result();
					}
				});
			}
			
			// size for nonroot entity
			{
				SortableColumn<KindBreakdown, String> column = 
					new SortableColumn<KindBreakdown, String>(new TextCell()) {
					@Override
					public String getValue(KindBreakdown kind) {
						return kind.getNonRootSize().toString();
					}
				};
				
				kindTable.addColumn(column, "Size(Non Root)");
				
				kindSortHandler.setComparator(column, new Comparator<KindBreakdown>() {
					@Override
					public int compare(KindBreakdown arg0, KindBreakdown arg1) {
						return ComparisonChain.start().compare(
								arg0.getNonRootSize(), arg1.getNonRootSize()).result();
					}
				});
			}
		}
		
		// init UI binders
		initWidget(uiBinder.createAndBindUi(this));
		
		// init chart types	
		// TODO : use i18n message
		chartTypeChoice.addItem("Bar Chart", CHART_TYPE_BAR);
		chartTypeChoice.addItem("Pie Chart", CHART_TYPE_PIE);
		chartTypeChoice.addItem("Column Chart", CHART_TYPE_COLUMN);
		
		// ================init filter configuration panel===================

		// init namespace filters		
		namespaceSingleConfig = new CountSizeSingleFilterPanel(NAMESPACE_RADIO_GROUP); 
		namespaceSingleConfig.addOnChangeHandler(new CountSizeSingleFilterPanel.OnChangeHandler() {
			@Override
			public void onChange(CountSizeFilterChoice val) {
				refreshNamespaceChart();
			}
		});
		
		namespaceMultiConfig = new CountSizeMultiFilterPanel();
		namespaceMultiConfig.addOnChangeHandler(new CountSizeMultiFilterPanel.OnChangeHandler() {
			@Override
			public void onChange(Set<CountSizeFilterChoice> vals) {
				refreshNamespaceChart();
			}
		});
		
		// has to be added in this order
		namespaceChartConfig.add(namespaceSingleConfig);
		namespaceChartConfig.add(namespaceMultiConfig);	
		namespaceChartConfig.setAnimationEnabled(true);
		namespaceChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);	// bar chart will be selected by default
		
		// init property type filters
		propertyTypeSingleConfig = new CountSizeSingleFilterPanel(PROPERTY_TYPE_RADIO_GROUP); 
		propertyTypeSingleConfig.addOnChangeHandler(new CountSizeSingleFilterPanel.OnChangeHandler() {
			@Override
			public void onChange(CountSizeFilterChoice val) {
				refreshPropertyTypeChart();
			}
		});
		
		propertyTypeMultiConfig = new CountSizeMultiFilterPanel();
		propertyTypeMultiConfig.addOnChangeHandler(new CountSizeMultiFilterPanel.OnChangeHandler() {
			@Override
			public void onChange(Set<CountSizeFilterChoice> vals) {
				refreshPropertyTypeChart();
			}
		});
		
		// has to be added in this order
		propertyTypeChartConfig.add(propertyTypeSingleConfig);
		propertyTypeChartConfig.add(propertyTypeMultiConfig);
		propertyTypeChartConfig.setAnimationEnabled(true);
		propertyTypeChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);	// bar chart will be selected by default
		
		// init kind filters
		kindSingleConfig = new KindSingleFilterPanel(KIND_RADIO_GROUP); 
		kindSingleConfig.addOnChangeHandler(new KindSingleFilterPanel.OnChangeHandler() {
			@Override
			public void onChange(KindFilterChoice val) {
				refreshKindChart();
			}
		});
		
		kindMultiConfig = new KindMultiFilterPanel();
		kindMultiConfig.addOnChangeHandler(new KindMultiFilterPanel.OnChangeHandler() {
			@Override
			public void onChange(Set<KindFilterChoice> vals) {
				refreshKindChart();
			}
		});
		
		// has to be added in this order
		kindChartConfig.add(kindSingleConfig);
		kindChartConfig.add(kindMultiConfig);
		kindChartConfig.setAnimationEnabled(true);
		kindChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);	// bar chart will be selected by default
	}

	@Override
	public void setPresenter(Presenter listener) {
		this.listener = listener;
	}

	private void refreshNamespaceChart() {
		VisualizationUtils.loadVisualizationApi(new Runnable() {
			@Override
			public void run() {
				String val = chartTypeChoice.getValue(chartTypeChoice.getSelectedIndex());
				
				// init data provider here instead of constructor to show the loading icon
				ListDataProvider<NamespaceBreakdown> namespaceProvider = 
					new ListDataProvider<NamespaceBreakdown>(namespaceBreakdownList);
				namespaceProvider.addDataDisplay(namespaceTable);
				namespaceSortHandler.setDataProvider(namespaceProvider);
				
				if (CHART_TYPE_PIE.equals(val)) {
					namespaceChart.setWidget(
							new PieChart(namespacePieChartDataTable(), namespacePieOptions()));
					namespaceChartConfig.showWidget(CONFIG_IDX_PIE);
				} else if (CHART_TYPE_BAR.equals(val)) {
					namespaceChart.setWidget(new BarChart(namespaceBarColumnChartDataTable(), namespaceBarOptions()));
					namespaceChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);
				} else if (CHART_TYPE_COLUMN.equals(val)) {
					namespaceChart.setWidget(new ColumnChart(namespaceBarColumnChartDataTable(), namespaceColumnOptions()));
					namespaceChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);
				}
			}
		}, PieChart.PACKAGE);
	}
	
	private ColumnChart.Options namespaceColumnOptions() {
		ColumnChart.Options options = ColumnChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}
	
	private BarChart.Options namespaceBarOptions() {
		BarChart.Options options = BarChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}
	
	private PieChart.Options namespacePieOptions() {
		PieChart.Options options = PieChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}
	
	private List<NamespaceBreakdown> selectedNamespaceList() {
		List<NamespaceBreakdown> selectedList = new LinkedList<NamespaceBreakdown>();
		
		for (NamespaceBreakdown breakdown : namespaceBreakdownList) {
			if (namespaceSelectionModel.isSelected(breakdown)) {
				selectedList.add(breakdown);
			}
		}
		
		// if nothing is selected, then select all records
		if (selectedList.isEmpty()) {
			selectedList = namespaceBreakdownList;
		}
		
		return selectedList;
	}
	
	private DataTable namespacePieChartDataTable() {
		CountSizeFilterChoice selectedChoice = namespaceSingleConfig.selectedValue();
		
		DataTable data = DataTable.create();
		data.addColumn(ColumnType.STRING, "Namespace");
		
		if (selectedChoice == CountSizeFilterChoice.COUNT) {
			data.addColumn(ColumnType.NUMBER, "Count");	
		} else {
			data.addColumn(ColumnType.NUMBER, "Size");	
		}
		
		List<NamespaceBreakdown> selectedList = selectedNamespaceList();

		data.addRows(selectedList.size());

		int rowCount = 0;
		
		for (NamespaceBreakdown breakdown : selectedList) {
			data.setValue(rowCount, 0, breakdown.getNamespace());
			
			if (selectedChoice == CountSizeFilterChoice.COUNT) {
				data.setValue(rowCount, 1, breakdown.getCount());	
			} else {
				data.setValue(rowCount, 1, breakdown.getBytes());	
			}
				
			rowCount ++;	
		}
		
		return data;
	}
	
	private DataTable namespaceBarColumnChartDataTable() {
		Set<CountSizeFilterChoice> selectedCols = namespaceMultiConfig.selectedValues();
		
		DataTable data = DataTable.create();
		data.addColumn(ColumnType.STRING, "Namespace");
		
		if (selectedCols.contains(CountSizeFilterChoice.COUNT)) {
			data.addColumn(ColumnType.NUMBER, "Count");
		}
		
		if (selectedCols.contains(CountSizeFilterChoice.SIZE)) {
			data.addColumn(ColumnType.NUMBER, "Size");
		}
		
		List<NamespaceBreakdown> selectedList = selectedNamespaceList();
		data.addRows(selectedList.size());

		int rowCount = 0;
		
		for (NamespaceBreakdown breakdown : selectedList) {
			data.setValue(rowCount, 0, breakdown.getNamespace());
			
			int colIdx = 1;
			
			if (selectedCols.contains(CountSizeFilterChoice.COUNT)) {
				data.setValue(rowCount, colIdx ++, breakdown.getCount());
			}
			
			if (selectedCols.contains(CountSizeFilterChoice.SIZE)) {
				data.setValue(rowCount, colIdx ++, breakdown.getBytes());
			}
				
			rowCount ++;	
		}
		
		return data;
	}

	private void refreshPropertyTypeChart() {
		VisualizationUtils.loadVisualizationApi(new Runnable() {
			@Override
			public void run() {
				String val = chartTypeChoice.getValue(chartTypeChoice.getSelectedIndex());
				
				// init data provider here instead of constructor to show the loading icon
				ListDataProvider<PropertyTypeBreakdown> propertiesProvider = 
					new ListDataProvider<PropertyTypeBreakdown>(propertyBreakdownList);
				propertiesProvider.addDataDisplay(propertyTypeTable);
				propertySortHandler.setDataProvider(propertiesProvider);
				
				if (CHART_TYPE_PIE.equals(val)) {
					propertyTypeChart.setWidget(
							new PieChart(propertyTypePieChartDataTable(), propertyTypePieOptions()));
					propertyTypeChartConfig.showWidget(CONFIG_IDX_PIE);
				} else if (CHART_TYPE_BAR.equals(val)) {
					propertyTypeChart.setWidget(
							new BarChart(propertyTypeBarColumnChartDataTable(), propertyTypeBarOptions()));
					propertyTypeChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);
				} else if (CHART_TYPE_COLUMN.equals(val)) {
					propertyTypeChart.setWidget(
							new ColumnChart(propertyTypeBarColumnChartDataTable(), propertyTypeColumnOptions()));
					propertyTypeChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);
				}
			}
		}, PieChart.PACKAGE);
	}
	
	private ColumnChart.Options propertyTypeColumnOptions() {
		ColumnChart.Options options = ColumnChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}

	private BarChart.Options propertyTypeBarOptions() {
		BarChart.Options options = BarChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}
	
	private PieChart.Options propertyTypePieOptions() {
		PieChart.Options options = PieChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}

	private List<PropertyTypeBreakdown> selectedPropertyTypeList() {
		List<PropertyTypeBreakdown> selectedList = new LinkedList<PropertyTypeBreakdown>();
		
		for (PropertyTypeBreakdown breakdown : propertyBreakdownList) {
			if (propertySelectionModel.isSelected(breakdown)) {
				selectedList.add(breakdown);
			}
		}
		
		// if nothing is selected, then select all records
		if (selectedList.isEmpty()) {
			selectedList = propertyBreakdownList;
		}
		
		return selectedList;
	}
	
	private DataTable propertyTypePieChartDataTable() {
		CountSizeFilterChoice selectedChoice = propertyTypeSingleConfig.selectedValue();
		
		DataTable data = DataTable.create();
		data.addColumn(ColumnType.STRING, "Property Type");
		
		if (selectedChoice == CountSizeFilterChoice.COUNT) {
			data.addColumn(ColumnType.NUMBER, "Count");	
		} else {
			data.addColumn(ColumnType.NUMBER, "Size");	
		}
		
		List<PropertyTypeBreakdown> selectedList = selectedPropertyTypeList();

		data.addRows(selectedList.size());

		int rowCount = 0;
		
		for (PropertyTypeBreakdown breakdown : selectedList) {
			data.setValue(rowCount, 0, breakdown.getPropertyType());
			
			if (selectedChoice == CountSizeFilterChoice.COUNT) {
				data.setValue(rowCount, 1, breakdown.getCount());	
			} else {
				data.setValue(rowCount, 1, breakdown.getBytes());	
			}
				
			rowCount ++;	
		}
		
		return data;
	}
	
	private DataTable propertyTypeBarColumnChartDataTable() {
		Set<CountSizeFilterChoice> selectedCols = propertyTypeMultiConfig.selectedValues();
		
		DataTable data = DataTable.create();
		data.addColumn(ColumnType.STRING, "Property Type");
		
		if (selectedCols.contains(CountSizeFilterChoice.COUNT)) {
			data.addColumn(ColumnType.NUMBER, "Count");
		}
		
		if (selectedCols.contains(CountSizeFilterChoice.SIZE)) {
			data.addColumn(ColumnType.NUMBER, "Size");
		}
		
		List<PropertyTypeBreakdown> selectedList = selectedPropertyTypeList();
		data.addRows(selectedList.size());

		int rowCount = 0;
		
		for (PropertyTypeBreakdown breakdown : selectedList) {
			data.setValue(rowCount, 0, breakdown.getPropertyType());
			
			int colIdx = 1;
			
			if (selectedCols.contains(CountSizeFilterChoice.COUNT)) {
				data.setValue(rowCount, colIdx ++, breakdown.getCount());
			}
			
			if (selectedCols.contains(CountSizeFilterChoice.SIZE)) {
				data.setValue(rowCount, colIdx ++, breakdown.getBytes());
			}
				
			rowCount ++;	
		}
		
		return data;
	}

	private BarChart.Options kindBarOptions() {
		BarChart.Options options = BarChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}

	private ColumnChart.Options kindColumnOptions() {
		ColumnChart.Options options = ColumnChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}
	
	private PieChart.Options kindPieOptions() {
		PieChart.Options options = PieChart.Options.create();
		options.set3D(false);
		options.setWidth(400);
		options.setHeight(300);
		options.setLegend(LegendPosition.LEFT);
		
		return options;
	}

	private List<KindBreakdown> selectedKindList() {
		List<KindBreakdown> selectedList = new LinkedList<KindBreakdown>();
		
		for (KindBreakdown breakdown : kindBreakdownList) {
			if (kindSelectionModel.isSelected(breakdown)) {
				selectedList.add(breakdown);
			}
		}
		
		// if nothing is selected, then select all records
		if (selectedList.isEmpty()) {
			selectedList = kindBreakdownList;
		}
		
		return selectedList;
	}
	
	private DataTable kindPieChartDataTable() {
		KindFilterChoice selectedChoice = kindSingleConfig.selectedValue();
		
		DataTable data = DataTable.create();
		data.addColumn(ColumnType.STRING, "Name");
		
		switch (selectedChoice) {
		case COUNT_ALL :
			data.addColumn(ColumnType.NUMBER, "Count(All)");	
			break;
		case SIZE_ALL :
			data.addColumn(ColumnType.NUMBER, "Size(All)");
			break;
		case COUNT_ROOT :
			data.addColumn(ColumnType.NUMBER, "Count(Root)");
			break;
		case SIZE_ROOT :
			data.addColumn(ColumnType.NUMBER, "Size(Root)");
			break;
		case COUNT_NON_ROOT :
			data.addColumn(ColumnType.NUMBER, "Count(Non Root)");
			break;
		case SIZE_NON_ROOT :
			data.addColumn(ColumnType.NUMBER, "Size(Non Root)");
			break;
		}
		
		List<KindBreakdown> selectedList = selectedKindList();

		data.addRows(selectedList.size());

		int rowCount = 0;
		
		for (KindBreakdown breakdown : selectedList) {
			data.setValue(rowCount, 0, breakdown.getKindName());
			
			switch (selectedChoice) {
			case COUNT_ALL :
				data.setValue(rowCount, 1, breakdown.getCount());
				break;
			case SIZE_ALL :
				data.setValue(rowCount, 1, breakdown.getSize());
				break;
			case COUNT_ROOT :
				data.setValue(rowCount, 1, breakdown.getRootCount());
				break;
			case SIZE_ROOT :
				data.setValue(rowCount, 1, breakdown.getRootSize());
				break;
			case COUNT_NON_ROOT :
				data.setValue(rowCount, 1, breakdown.getNonRootCount());
				break;
			case SIZE_NON_ROOT :
				data.setValue(rowCount, 1, breakdown.getNonRootSize());
				break;
			}

			rowCount ++;	
		}
		
		return data;
	}
	
	private DataTable kindBarColumnChartDataTable() {
		Set<KindFilterChoice> selectedCols = kindMultiConfig.selectedValues();
		
		DataTable data = DataTable.create();
		data.addColumn(ColumnType.STRING, "Name");
		
		if (selectedCols.contains(KindFilterChoice.COUNT_ALL)) {
			data.addColumn(ColumnType.NUMBER, "Count(All)");
		}
		
		if (selectedCols.contains(KindFilterChoice.SIZE_ALL)) {
			data.addColumn(ColumnType.NUMBER, "Size(All)");
		}
		
		if (selectedCols.contains(KindFilterChoice.COUNT_ROOT)) {
			data.addColumn(ColumnType.NUMBER, "Count(Root)");
		}
		
		if (selectedCols.contains(KindFilterChoice.SIZE_ROOT)) {
			data.addColumn(ColumnType.NUMBER, "Size(Root)");
		}
		
		if (selectedCols.contains(KindFilterChoice.COUNT_NON_ROOT)) {
			data.addColumn(ColumnType.NUMBER, "Count(Non Root)");
		}
		
		if (selectedCols.contains(KindFilterChoice.SIZE_NON_ROOT)) {
			data.addColumn(ColumnType.NUMBER, "Size(Non Root)");
		}
		
		List<KindBreakdown> selectedList = selectedKindList();
		data.addRows(selectedList.size());

		int rowCount = 0;
		
		for (KindBreakdown breakdown : selectedList) {
			data.setValue(rowCount, 0, breakdown.getKindName());
			
			int colIdx = 1;
			
			if (selectedCols.contains(KindFilterChoice.COUNT_ALL)) {
				data.setValue(rowCount, colIdx ++, breakdown.getCount());
			}
			
			if (selectedCols.contains(KindFilterChoice.SIZE_ALL)) {
				data.setValue(rowCount, colIdx ++, breakdown.getSize());
			}
			
			if (selectedCols.contains(KindFilterChoice.COUNT_ROOT)) {
				data.setValue(rowCount, colIdx ++, 
						breakdown.getRootCount() == null ? 0l : breakdown.getRootCount());
			}
			
			if (selectedCols.contains(KindFilterChoice.SIZE_ROOT)) {
				data.setValue(rowCount, colIdx ++, 
						breakdown.getRootSize() == null ? 0l : breakdown.getRootSize());
			}
			
			if (selectedCols.contains(KindFilterChoice.COUNT_NON_ROOT)) {
				data.setValue(rowCount, colIdx ++, 
						breakdown.getNonRootCount() == null ? 0l : breakdown.getNonRootCount());
			}
			
			if (selectedCols.contains(KindFilterChoice.SIZE_NON_ROOT)) {
				data.setValue(rowCount, colIdx ++, 
						breakdown.getNonRootSize() == null ? 0l : breakdown.getNonRootSize());
			}
				
			rowCount ++;	
		}
		
		return data;
	}
	
	@UiHandler("chartTypeChoice")
	void onChartTypeChange(ChangeEvent event) {
		// refresh all charts
		refreshPropertyTypeChart();
		refreshNamespaceChart();
		refreshKindChart();
	}

	private void refreshKindChart() {
		VisualizationUtils.loadVisualizationApi(new Runnable() {
			@Override
			public void run() {
				String val = chartTypeChoice.getValue(chartTypeChoice.getSelectedIndex());
				
				// init data provider here instead of constructor to show the loading icon
				ListDataProvider<KindBreakdown> kindsProvider = 
					new ListDataProvider<KindBreakdown>(kindBreakdownList);
				kindsProvider.addDataDisplay(kindTable);
				kindSortHandler.setDataProvider(kindsProvider);
				
				if (CHART_TYPE_PIE.equals(val)) {
					kindChart.setWidget(new PieChart(kindPieChartDataTable(), kindPieOptions()));
					kindChartConfig.showWidget(CONFIG_IDX_PIE);
				} else if (CHART_TYPE_BAR.equals(val)) {
					kindChart.setWidget(new BarChart(kindBarColumnChartDataTable(), kindBarOptions()));
					kindChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);
				} else if (CHART_TYPE_COLUMN.equals(val)) {
					kindChart.setWidget(new ColumnChart(kindBarColumnChartDataTable(), kindColumnOptions()));
					kindChartConfig.showWidget(CONFIG_IDX_BAR_COLUMN);
				}
			}
		}, PieChart.PACKAGE);
	}

	@Override
	public void initKindNames(Collection<String> kindNames) {
		ListBoxUtil.resetItems(kindChoice, SELECT_ALL, kindNames);
	}

	@UiHandler("kindChoice")
	void onKindChoiceChange(ChangeEvent event) {		
		onFilterChange();
	}

	@Override
	public void initNamespaceNames(Collection<String> namespaceNames) {
		ListBoxUtil.resetItems(namespaceChoice, SELECT_ALL, namespaceNames);
	}

	@UiHandler("namespaceChoice")
	void onNamespaceChoiceChange(ChangeEvent event) {		
		onFilterChange();
	}
	
	private void onFilterChange() {
		String selectedNamespace = ListBoxUtil.selectedVal(namespaceChoice);
		String selectedKind = ListBoxUtil.selectedVal(kindChoice);
		
		this.listener.onFilterChange(selectedNamespace, selectedKind);
	}

	@Override
	public void updateSummary(Summary summary) {
		// TODO : i18n
		String desc = "Last updated " + summary.getDate().toString() + ", total number of entities " + summary.getNoEntities()
			+ ", size of all entities " + summary.getSizeEntities();
	
		descElem.setInnerHTML(SafeHtmlUtils.fromString(desc).asString());
	}

	@Override
	public void updateNamespaceBreakdown(List<NamespaceBreakdown> breakdownList) {
		// update namespaceTable		
		this.namespaceBreakdownList = breakdownList;
		
		// update chart
		refreshNamespaceChart();
	}

	@Override
	public void updatePropertyTypeBreakdown(List<PropertyTypeBreakdown> breakdownList) {
		// update table
		this.propertyBreakdownList = breakdownList;
		
		// update chart
		refreshPropertyTypeChart();
	}

	@Override
	public void updateKindBreakdown(List<KindBreakdown> breakdownList) {
		this.kindBreakdownList = breakdownList;
		
		// update chart
		refreshKindChart();
	}
}
